#include "catch.hpp"

#include "common/fs.h"

#include <unistd.h>
#include <sys/stat.h>
#include <sys/socket.h>
#include <sys/un.h>
#include <cstring>
#include <fstream>


using namespace std;
using namespace evm;

TEST_CASE("fs_path tests", "[fs_path]") {
    {
        string p = "/usr/lib/sendmail.cf";
        fs::path p1 = p;
        REQUIRE(p1 == p);

        auto p2 = p1 / "..";
        p2 /= "sendmail.info";
        p2 /= "..";
        WARN(p2.c_str());
        REQUIRE(p2.c_str() == p2.native());
        REQUIRE(p2 == p2.c_str());

        p2 = std::move(p1);
        REQUIRE(p1 == "");
        REQUIRE(p1.empty());
        REQUIRE(p2 == p);
    }

    {
        fs::path p1; // empty path
        p1 += "var"; // does not insert a separator
        p1 += "lib"; // does not insert a separator
        REQUIRE(p1 == "varlib");

        p1.clear();
        REQUIRE(p1.empty());
    }

    {
        REQUIRE(fs::path("/foo").remove_filename() == "/");
        REQUIRE(fs::path("/").remove_filename() == "");
        REQUIRE(fs::path("/foo").replace_filename("bar") == "/bar");
        REQUIRE(fs::path("/").replace_filename("bar") == "bar");
        REQUIRE(fs::path("/foo/bar.jpeg").replace_extension("jpg") == "/foo/bar.jpg");
    }

    {
        fs::path p1 = "/foo/bar";
        WARN(p1.root_name());
        WARN(p1.root_directory());
        WARN(p1.relative_path());
        fs::path p2 = p1.root_name();
        p2 += p1.root_directory();
        p2 += p1.relative_path();
        REQUIRE(p1 == p2);
        REQUIRE(p1.root_path() == p1.root_name() / p1.root_directory());
        REQUIRE(p1.root_path() / p1.relative_path() == p1);
    }

    {
        REQUIRE(fs::path("/var/tmp/example.txt").parent_path() == "/var/tmp");
        REQUIRE(fs::path("/").parent_path() == "");
        REQUIRE(fs::path("/var/tmp/.").parent_path() == "/var/tmp");
    }

    {
        REQUIRE(fs::path("/foo/bar.txt").filename() == "bar.txt");
        REQUIRE(fs::path("/foo/.bar").filename() == ".bar");
        REQUIRE(fs::path("/foo/bar/").filename() == ".");
        REQUIRE(fs::path("/foo/.").filename() == ".");
        REQUIRE(fs::path("/foo/..").filename() == "..");
        REQUIRE(fs::path(".").filename() == ".");
        REQUIRE(fs::path("..").filename() == "..");
        REQUIRE(fs::path("/").filename() == "/");
    }

    {
        REQUIRE(fs::path("/foo/bar.txt").stem() == "bar");
        REQUIRE(fs::path("/foo/.bar").stem() == "");
        REQUIRE(fs::path("foo.bar.baz.tar").stem() == "foo.bar.baz");
    }
}

#define REQUIRE_IF_EXISTS(predicate, path) \
    if (fs::exists(path)) \
        REQUIRE(predicate(path));

TEST_CASE("fs_ops tests", "[fs_ops]") {
    {
        REQUIRE_IF_EXISTS(fs::is_block_file, "/dev/sda");
        REQUIRE_IF_EXISTS(fs::is_character_file, "/dev/null");
        REQUIRE_IF_EXISTS(fs::is_directory, "/");
        REQUIRE_IF_EXISTS(fs::is_regular_file, "/bin/ls");
        REQUIRE_IF_EXISTS(fs::is_symlink, "/bin/sh");
    }

    {
        // create files of different kinds
        fs::create_directory("sandbox");
        REQUIRE(fs::exists("sandbox"));

        std::ofstream("sandbox/file"); // create regular file
        fs::create_directory("sandbox/dir");
        mkfifo("sandbox/pipe", 0644);
        struct sockaddr_un addr;
        addr.sun_family = AF_UNIX;
        std::strcpy(addr.sun_path, "sandbox/sock");
        int fd = socket(PF_UNIX, SOCK_STREAM, 0);
        bind(fd, (struct sockaddr*)&addr, sizeof addr);
        fs::create_symlink("file", "sandbox/symlink");

        REQUIRE(fs::is_regular_file("sandbox/file"));
        REQUIRE(fs::is_directory("sandbox/dir"));
        REQUIRE(fs::is_fifo("sandbox/pipe"));
        REQUIRE(fs::is_socket("sandbox/sock"));
        REQUIRE(fs::is_symlink("sandbox/symlink"));
        REQUIRE(!fs::exists("sandbox/no"));

        // cleanup
        close(fd);
        fs::remove_all("sandbox");
        REQUIRE(!fs::exists("sandbox"));
    }

}
